﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using System.Text;
using System.Drawing;
using GRV11;

namespace gpuimp.core
{
    public class Paintbrush : Tool
    {
        /// <summary>
        /// basic tool constructor
        /// </summary>
        public Paintbrush()
            : base()
        {
            type = ToolType.Brush;
            name = "Paintbrush";
        }

        /// <summary>
        /// create a tool with specified setting already
        /// </summary>
        /// <param name="tool"></param>
        public Paintbrush(Tool tool)
            : base(tool)
        {
            type = ToolType.Brush;
            name = "Paintbrush";
        }


        protected override void setToolShader(PointF position, List<PointF> pastPositions, PointF viewSize, System.Windows.Media.Color color, float pressure)
        {
            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, this.shader);

            int shaderDiffuse = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "diffuseMap");
            int shaderCursor1 = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "cursorPos1");
            int shaderCursor2 = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "cursorPos2");
            int shaderCursor3 = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "cursorPos3");
            int shaderImage = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "imageSize");
            int shaderViewport = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "viewportSize");
            int shaderCanvas = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "canvasPos");
            int shaderColor = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "brushColor");
            int shaderBrushsize = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "brushSize");
            int shaderBrushhardness = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "brushHardness");
            int shaderMask = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "selectionMask");

            float zoom = 1 / CanvasManager.CurrentCanvas.Zoom;
            position.X *= zoom;
            position.Y *= zoom;
            position.X += (CanvasManager.CurrentCanvas.ImageSize.X - CanvasManager.CurrentCanvas.CanvasArea.X * zoom) / 2 - CanvasManager.CurrentCanvas.Position.X * zoom;
            position.Y += (CanvasManager.CurrentCanvas.ImageSize.Y - CanvasManager.CurrentCanvas.CanvasArea.Y * zoom) / 2 - CanvasManager.CurrentCanvas.Position.Y * zoom;




            CanvasManager.gr.glUniform2fARB(shaderCursor1, position.X, position.Y);
            if (pastPositions.Count > 1)
            {
                PointF p1 = pastPositions[1];
                //p1.X /= zoom;
                //p1.Y /= zoom;
                p1.X *= zoom;
                p1.Y *= zoom;
                p1.X += (CanvasManager.CurrentCanvas.ImageSize.X - CanvasManager.CurrentCanvas.CanvasArea.X * zoom) / 2 - CanvasManager.CurrentCanvas.Position.X * zoom;
                p1.Y += (CanvasManager.CurrentCanvas.ImageSize.Y - CanvasManager.CurrentCanvas.CanvasArea.Y * zoom) / 2 - CanvasManager.CurrentCanvas.Position.Y * zoom;


                PointF p2 = pastPositions[0];
                //p2.X /= zoom;
                //p2.Y /= zoom;
                p2.X *= zoom;
                p2.Y *= zoom;
                p2.X += (CanvasManager.CurrentCanvas.ImageSize.X - CanvasManager.CurrentCanvas.CanvasArea.X * zoom) / 2 - CanvasManager.CurrentCanvas.Position.X * zoom;
                p2.Y += (CanvasManager.CurrentCanvas.ImageSize.Y - CanvasManager.CurrentCanvas.CanvasArea.Y * zoom) / 2 - CanvasManager.CurrentCanvas.Position.Y * zoom;
                

                CanvasManager.gr.glUniform2fARB(shaderCursor2, p1.X, p1.Y);
                CanvasManager.gr.glUniform2fARB(shaderCursor3, p2.X, p2.Y);
            }
            else
            {

                CanvasManager.gr.glUniform2fARB(shaderCursor2, position.X, position.Y);
                CanvasManager.gr.glUniform2fARB(shaderCursor3, position.X, position.Y);
            }
            CanvasManager.gr.glUniform2fARB(shaderImage, CanvasManager.CurrentCanvas.ImageSize.X, CanvasManager.CurrentCanvas.ImageSize.Y);
            CanvasManager.gr.glUniform2fARB(shaderViewport, viewSize.X, viewSize.Y);
            CanvasManager.gr.glUniform2fARB(shaderCanvas, 0, 0); //moving the canvas would go here
            CanvasManager.gr.glUniform1fARB(shaderBrushsize, size * pressure);
            CanvasManager.gr.glUniform1fARB(shaderBrushhardness, hardness);
            CanvasManager.gr.glUniform4fARB(shaderColor, color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f);
            CanvasManager.gr.glUniform1iARB(shaderDiffuse, 0);
            CanvasManager.gr.glUniform1iARB(shaderMask, 1);
        }

        public override void draw(GRControl control, PointF position, List<PointF> pastPositions, System.Windows.Media.Color color, float pressure)
        {
            applying = true;
            //get render control
            GR gr = control.GetGR();
            //height and width of the image
            int activeLayer = CanvasManager.CurrentCanvas.LayerManager.ActiveLayer;
            int width = CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.GetWidth();
            int height = CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.GetHeight();
            float brushSize = size * pressure * 0.5f;

            
            gr.glLoadIdentity();

            gr.glPushMatrix();

            setToolShader(position, pastPositions, new PointF(control.Width, control.Height), color, pressure);
            //clear render
            gr.glClear(GR.GL_COLOR_BUFFER_BIT);
            //get IDs of the two textures to use
            int fboTexture = CanvasManager.CurrentCanvas.grFBOTexture.GetOpenGLTextureID();
            int layerTexture = CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.GetOpenGLTextureID();

            //bind FBO
            gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[0]);
            //bind intermediate texture to fbo
            gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, fboTexture, 0);

            //set to draw to color 0 buffer
            int[] buffers = { GR.GL_COLOR_ATTACHMENT0_EXT };
            gr.glDrawBuffers(1, buffers);

            //set to render to texture size
            setupToolViewport(gr);

            //set the original texture active
            CanvasManager.gr.glActiveTexture(GR.GL_TEXTURE0);
            CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.SetAsActiveTexture(gr);
            //set the selection mask as active
            CanvasManager.gr.glActiveTexture(GR.GL_TEXTURE1);
            CanvasManager.CurrentCanvas.Selection.selectionMask.SetAsActiveTexture(CanvasManager.gr);
            CanvasManager.gr.glActiveTexture(GR.GL_TEXTURE0);

            //draw quad with this texture
            CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].Draw(gr, false, false);

            //unbind FBO
            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
            //remove shader
            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);

            //rebind fbo
            gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[1]);
            //bind final output texture
            gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, layerTexture, 0);
            //switch to copy texture
            setCopyShader();
            //render quad with intermediate texture
            //set the intermediate texture active
            CanvasManager.CurrentCanvas.grFBOTexture.SetAsActiveTexture(gr);
            CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].Draw(CanvasManager.gr, false, false);

            //unbind FBO
            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
            //remove shader
            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);
            //gr.glDisable(GR.GL_SCISSOR_TEST);
        }

        public override void setFragProg(String frag)
        {
            //   this.fragProg = fragProg;
            this.fragProg = @"
//existing texture
uniform sampler2D diffuseMap;
//selection mask
uniform sampler2D selectionMask;
uniform float brushHardness;
uniform vec2 imageSize;
//color to draw with
uniform vec4 brushColor;
//uv coordinate of quad image is applied to
varying vec2 texCoord;

varying float size;
varying vec2 dir2, dir1;
varying float len, iterations;
varying vec2 cursorPos;

void main(void)
{
    //get the existing texture
    vec4 diffuse = texture2D(diffuseMap, texCoord);

    float dist = 0;
    float amount = 0;
    
    float percent = 0;
    vec2 dir3;
    vec2 offset;
    vec2 aspectRatio = vec2(1, imageSize.x / imageSize.y);
    for(int i = 0; i < iterations; i++)
    {
       percent = i / (iterations - 1);

       dir3 = dir2 * (1-percent);
       dir3 += dir1 * percent;
       dir3 = normalize(dir3);       
       offset = cursorPos + dir3 * len * percent;
       
       dist = length( (offset - texCoord) / aspectRatio);
       dist = 1 - dist + size;
       amount += max(pow(dist, brushHardness * 15.0), 0.0); //15 is a magic number to make 100 be a hard brush

    }
    

    amount = clamp(amount, 0.0, 1.0);
    amount *= brushColor.a;
    amount = clamp(amount, 0.0, 1.0);
    amount *= texture2D(selectionMask, texCoord).r;
    gl_FragColor = (1 - amount) * diffuse + amount * brushColor;
}
";

        }

        public override void setVertProg(String vert)
        {
            //this.vertProg = vertProg;
            vertProg = @"
//last 3 positions, 1 is most recent
uniform vec2 cursorPos3, cursorPos2, cursorPos1;
//total size of image
uniform vec2 imageSize;
//size of viewport
uniform vec2 viewportSize;
//offset of viewport in image
uniform vec2 canvasPos;
//size of the brush - probably not accurate just yet
uniform float brushSize;
uniform float brushHardness;

varying vec2  texCoord;
varying vec2 dir2, dir1;
varying float iterations;
varying float len;

varying float size;
varying vec2 cursorPos;

void main(void)
{
   // Texture coordinate for screen aligned (in correct range):
   //texCoord = (vec2( gl_Position.x, - gl_Position.y ) + vec2( 1.0 ) ) / vec2( 2.0 );
   gl_Position = ftransform();
   texCoord = gl_MultiTexCoord0.xy;

   //convert screen to uv coordinate spaces

   //we have to translate the position of the cursor into position relative to 
   //the uv coordinates of the quad the layer texture is applied to
   //percent of the image the viewport covers
   vec2 viewportOffset = viewportSize / imageSize;
   
   vec2 cursorOffset = cursorPos1 / viewportSize;
   cursorOffset.y = 1 - cursorOffset.y;
   cursorPos1 = cursorOffset * viewportOffset;

   cursorOffset = cursorPos2 / viewportSize;
   cursorOffset.y = 1 - cursorOffset.y;
   cursorPos2 = cursorOffset * viewportOffset;

   cursorOffset = cursorPos3 / viewportSize;
   cursorOffset.y = 1 - cursorOffset.y;
   cursorPos3 = cursorOffset * viewportOffset;
   
   //convert brush size to texcoord coordinate space
   brushSize /= imageSize;
   size = brushSize;

   dir2 = cursorPos2 - cursorPos3;
   dir1 = cursorPos1 - cursorPos2;

   //paintbrush iteration stuff
   len = length(dir1);
   iterations = len / size;
   
   dir2 = normalize(dir2);
   dir1 = normalize(dir1);

   cursorPos = cursorPos2;
}
";
        }
    }
}
